// -----------------------------------------------------------------------
//  <copyright file="DispatchDomainEventInterceptor.cs" company="LiuliuSoft">
//      Copyright (c) 2022-2024 DaprPlus. All rights reserved.
//  </copyright>
//  <site>https://dapr.plus</site>
//  <last-editor>郭明锋</last-editor>
//  <last-date>2024/12/10 15:39:33</last-date>
// -----------------------------------------------------------------------

using Microsoft.EntityFrameworkCore.Diagnostics;


namespace DaprPlus.EntityFrameworkCore;

/// <summary>
/// 领域事件调度拦截器
/// 在事务提交之前，对服务内发送模型中的领域事件
/// </summary>
public class DispatchDomainEventInterceptor(IServiceProvider provider) : SaveChangesInterceptor
{
    public override int SavedChanges(SaveChangesCompletedEventData eventData, int result)
    {
        ArgumentNullException.ThrowIfNull(eventData.Context);

        DispatchDomainEventsAsync(eventData.Context).GetAwaiter().GetResult();
        return base.SavedChanges(eventData, result);
    }

    public override async ValueTask<int> SavedChangesAsync(SaveChangesCompletedEventData eventData, int result,
        CancellationToken cancellationToken = new CancellationToken())
    {
        ArgumentNullException.ThrowIfNull(eventData.Context);
        await DispatchDomainEventsAsync(eventData.Context, cancellationToken);
        return await base.SavedChangesAsync(eventData, result, cancellationToken);
    }

    /// <summary>
    /// 发送领域事件，包括域事件处理程序使用相同的DbContext和"InstancePerLifetimeScope"或"scoped"生命周期产生的副作用
    /// </summary>
    protected virtual async Task DispatchDomainEventsAsync(DbContext dbContext, CancellationToken token = default)
    {
        var domainEntities = dbContext.ChangeTracker.Entries<IHasDomainEvent>().Select(m => m.Entity)
            .Where(e => e.DomainEvents.Count > 0).ToArray();
        if (domainEntities.Length == 0)
        {
            return;
        }

        //事件溯源
        var eventSourcedEntities = domainEntities.OfType<IEventSourced>().ToArray();
        if (eventSourcedEntities.Length > 0)
        {
            var eventStore = provider.GetService<IEventSourcedStore>();
            if (eventStore != null)
            {
                foreach (var eventSourcedEntity in eventSourcedEntities)
                {
                    var events = eventSourcedEntity.DomainEvents.ToArray();
                    eventSourcedEntity.ClearDomainEvents();
                    await eventStore.SaveEvents(((dynamic)eventSourcedEntity).Id, events);
                }
            }
        }

        //领域事件
        var domainEventBus = provider.GetService<IDomainEventBus>();
        if (domainEventBus != null)
        {
            foreach (var domainEntity in domainEntities)
            {
                var domainEvents = domainEntity.DomainEvents.ToArray();
                domainEntity.ClearDomainEvents();
                foreach (var domainEvent in domainEvents)
                {
                    await domainEventBus.PublishAsync(domainEvent, token);
                }
            }
        }
    }
}
